using System;
using System.Collections.Generic;
using System.Linq;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;


namespace ArkanoidX.Objects
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public partial class Ball : StatefulComponent, IPhysicalObject
    {
        public enum LifeState
        {
            Inactive,
            Activating,
            Active,
            Dead
        }

        TimeSpan _intitalTime;
        TimeSpan _kActivatingDuration = new TimeSpan(0, 0, 1);

        public abstract class State : StatefulComponent.State<Ball>
        {
            public Rectangle _reachableArea;
            public TimeSpan _lastGameTime = default(TimeSpan);
            public float _weight;

            public State(Texture2D texture, Vector2 size, Rectangle regionInSource, 
                Rectangle reachableArea, float weight)
                : base(texture, size, regionInSource)
            {
                _reachableArea = reachableArea;
                _weight = weight;
            }

            public State(Texture2D texture, Vector2 size, Rectangle reachableArea, 
                float weight)
                : base(texture, size)
            {
                _reachableArea = reachableArea;
                _weight = weight;
            }

            public State(Texture2D texture, Rectangle reachableArea, float weight)
                : base(texture)
            {
                _reachableArea = reachableArea;
                _weight = weight;
            }

            public float Weight
            {
                get { return _weight; }
            }

            public override void Update(Ball ball, GameTime gameTime)
            {
                float radius = this.Size.X / 2;

                if (_lastGameTime != default(TimeSpan))
                {
                    ball.Position += ball._velocity * (gameTime.TotalGameTime - _lastGameTime).Milliseconds;
                }
                _lastGameTime = gameTime.TotalGameTime;

                /*
                 * Collision detection
                 */

                // confine within reachable area

                if (ball.Position.X - radius < _reachableArea.Left)
                {
                    if (ball._velocity.X < 0)
                    {
                        ball._velocity.X = -ball._velocity.X;
                    }
                    ball.Position.X = _reachableArea.Left + radius;
                }
                if (ball.Position.X + radius > _reachableArea.Right)
                {
                    if (ball._velocity.X > 0)
                    {
                        ball._velocity.X = -ball._velocity.X;
                    }
                    ball.Position.X = _reachableArea.Right - radius;
                }
                if (ball.Position.Y - radius < _reachableArea.Top)
                {
                    if (ball._velocity.Y < 0)
                    {
                        ball._velocity.Y = -ball._velocity.Y;
                    }
                    ball.Position.Y = _reachableArea.Top + radius;
                }
                if (ball.Position.Y + ball.Radius > _reachableArea.Bottom)
                {   // lose one life
                    ball._lifeState = LifeState.Dead;
                    Debug.WriteLine(String.Format("ball {0} died", ball.GetHashCode()), 
                        System.Diagnostics.TraceEventType.Critical); 
                    return;
                }

                // detect collision with the racket
                ball._racket.Collides(ball);

                // detect collision with bricks
                ball._bricks.Collides(ball);

            }
        }

        public State[] _states;
        public int _iCurrentState;      // index to the current state of the racket

        public State CurrentState { get { return _states[_iCurrentState]; } }

        public bool initialHitPlayed = false;


        public Level Level
        {
            get;
            set;
        }

        public virtual LifeState _lifeState
        {
            get;
            set;
        }

        public virtual bool IsDead
        {
            get
            {
                return _lifeState == LifeState.Dead;
            }
        }

        public Racket _racket;       // Associated racket
        public Bricks _bricks;       // bricks to be hit by the ball

        public Vector2 Position;    // Position on screen
        public Vector2 _velocity;    // Velocity of the ball in terms of pixels per milisecond

        public Rectangle _arena;

        public LifeState CurrentLifeState
        {
            get
            {
                return _lifeState;
            }
        }

        /* required by interface IPhysicalObject */
        public float Weight
        {
            get
            {
                return CurrentState.Weight;
            }
        }

        public float Radius
        {
            get 
            {
                return CurrentState.HalfSize.X;
            }
        }

        public Vector2 Velocity
        {
            get { return _velocity; }
            set { _velocity = value; }
        }

        public Ball(Game game, Rectangle arena, Bricks bricks, Racket racket)
            : base(game)
        {
            // TODO: Construct any child components here
            _iCurrentState = 0;

            _racket = racket;
            _bricks = bricks;

            _lifeState = LifeState.Inactive;

            _arena = arena;

            _states = new State[] 
            {
                new NormalState(game.Content.Load<Texture2D>("ball0"), 
                    new Vector2(10, 10), arena, 1.0f)
            };
        }

        public Ball(Ball another) : base(another.Game)
        {
            _iCurrentState = another._iCurrentState;

            _racket = another._racket;
            _bricks = another._bricks;

            _lifeState = another._lifeState;

            _arena = another._arena;

            _velocity = another._velocity;
            Position = another.Position;

            _states = new State[]
            {
                new NormalState(base.Game.Content.Load<Texture2D>("ball0"), 
                    new Vector2(10, 10), _arena, 1.0f)
            };
        }


        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here

            base.Initialize();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (this._lifeState == LifeState.Inactive)
            {
                initialHitPlayed = false;
                _intitalTime = gameTime.TotalGameTime;
                this._lifeState = LifeState.Activating;
                return;
            }
            else if (this._lifeState == LifeState.Activating)
            {
                initialHitPlayed = false;
                KeyboardState kbs = Keyboard.GetState();
                if (gameTime.TotalGameTime - _intitalTime >= _kActivatingDuration || kbs.IsKeyDown(Keys.Space))
                {
                    this._lifeState = LifeState.Active;
                }
                else
                {
                    return;
                }
            }

            if (this._lifeState == LifeState.Active && !initialHitPlayed)
            {
                ((MainGame)Game).SoundResources.PlayHit();
                initialHitPlayed = true;
            }


            if (this._lifeState == LifeState.Dead)
            {
                return;
            }

            // TODO: Add your update code here
            CurrentState.Update(this, gameTime);

            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            if (this._lifeState == LifeState.Dead)
                return;

            // Get the current sprite batch
            SpriteBatch sBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));

            CurrentState.Draw(sBatch, Position, Color.White);

            base.Draw(gameTime);
        }

        public void PutInStartPosition()
        {
            FollowRacket();

            _velocity.X = 0.3f;
            _velocity.Y = -0.3f;

            _lifeState = LifeState.Inactive;
        }

        public void FollowRacket()
        {
            Position.X = _racket.Position.X;
            Position.Y = _racket.Position.Y - _racket.Rectangle.Height * 0.5f;
        }
    }
}

